홈으로 돌아가기
#OS

OS (25) 데이터 무결성과 보호 - 디스크는 믿을 수 없다

2026-02-08

데이터 무결성과 보호: 디스크는 믿을 수 없다 (Data Integrity & Protection)

백엔드 개발이나 시스템 프로그래밍을 하다 보면 우리는 암묵적인 전제를 하나 가집니다. "내가 파일 시스템에 write()를 성공했다면, 그 데이터는 안전하게 저장되었고 언제든 read() 하면 똑같은 값을 돌려줄 것이다."라는 전제죠.

하지만 운영체제와 스토리지의 깊은 곳을 들여다보면, 이 전제는 사실 거짓 에 가깝습니다. 디스크는 완벽하지 않으며, 생각보다 자주 데이터를 잃어버리거나 망가뜨립니다.

오늘은 OSTEP(Operating Systems: Three Easy Pieces)의 데이터 무결성과 보호(Data Integrity and Protection) 챕터를 통해, 신뢰할 수 없는 하드웨어 위에서 파일 시스템이 어떻게 데이터의 안전성 을 보장하는지 깊이 있게 파헤쳐 보겠습니다. RAID를 넘어, 실제 디스크 오류 모델과 이를 해결하기 위한 체크섬(Checksum), 그리고 잃어버린 쓰기(Lost Write) 문제까지 다룹니다.


1. 핵심 질문: 데이터 무결성을 어떻게 보장하는가?

저장 장치에 쓴 데이터가 안전하게 보호되고 있다는 것을 시스템은 어떻게 보장할까요? 만약 디스크가 몰래 데이터를 바꾼다면(Bit rot), 우리는 그것을 어떻게 알아채고 복구해야 할까요?

이 질문에 답하기 위해서는 먼저 '디스크가 어떻게 고장 나는가' 에 대한 모델링이 필요합니다.


2. 디스크 오류 모델: Fail-Stop vs Fail-Partial

과거의 RAID 시스템을 공부할 때는 디스크 오류 모델이 단순했습니다. 이를 실패-시-멈춤(Fail-Stop) 모델이라고 합니다.

하지만 현대의 디스크는 훨씬 교활하게 고장 납니다. 이를 부분-실패(Fail-Partial) 모델이라고 부릅니다.

이 부분 실패 모델에서 발생하는 오류는 크게 두 가지로 나뉩니다.

2.1. 숨어있는 섹터 에러 (Latent Sector Error, LSE)

LSE는 디스크의 특정 섹터나 섹터 그룹이 물리적으로 손상되어 읽을 수 없는 상태를 말합니다.

2.2. 블록 손상 (Block Corruption) - 조용한 오류(Silent Fault)

이것이 진짜 무서운 놈입니다. 데이터가 망가졌는데 디스크가 그 사실을 모르고, 에러도 리턴하지 않는 경우 입니다.

연구 결과에 따르면, 저가형 SATA 드라이브뿐만 아니라 고가의 SCSI 드라이브에서도 이러한 현상은 드물지만 분명히 발생한다고 합니다.


3. LSE(숨어있는 섹터 에러) 처리하기

LSE는 그나마 대처하기 쉽습니다. 디스크가 "에러 발생!"이라고 알려주기 때문입니다.

해결책: 중복(Redundancy) 활용

저장 시스템이 블록을 읽으려다 에러를 받으면, 시스템은 미리 저장해 둔 중복 정보 를 활용해 데이터를 복구합니다.

RAID-DP (Double Parity)의 등장

LSE가 빈번해지면서 RAID 5에도 문제가 생겼습니다. 만약 디스크 하나가 완전히 고장(Fail-Stop) 나서 재구성(Reconstruct)을 하고 있는데, 나머지 멀쩡한 줄 알았던 디스크 중 하나에서 LSE가 발견된다면? 복구할 정보가 부족해 데이터가 영영 손실됩니다. 이를 해결하기 위해 NetApp의 RAID-DP 같은 시스템은 두 개의 패리티 디스크 를 사용하여, 재구성 도중 LSE가 발생해도 데이터를 복구할 수 있도록 설계되었습니다. 비용은 더 들지만, 데이터 안전성은 훨씬 높아집니다.


4. 손상 검출의 핵심: 체크섬 (Checksum)

가장 어려운 문제인 '조용한 데이터 손상(Block Corruption)' 으로 넘어가 봅시다. 디스크가 뻔뻔하게 잘못된 데이터를 줄 때, OS는 이를 어떻게 감지할까요?

정답은 체크섬(Checksum) 입니다.

4.1. 체크섬이란?

데이터 청크(예: 4KB 블록)의 내용을 요약한 작은 데이터(예: 4~8 바이트)입니다. 데이터를 쓸 때 체크섬을 계산해서 같이 저장하고, 읽을 때 다시 계산해서 저장된 값과 비교합니다.

4.2. 체크섬 함수의 종류

체크섬 함수는 속도와 충돌(Collision, 다른 데이터인데 같은 체크섬이 나오는 확률) 가능성 사이에서 트레이드오프가 있습니다.

  1. XOR 체크섬:

    • 가장 간단합니다. 데이터를 4바이트 단위로 쪼개서 세로로 XOR 연산을 합니다.
    • 장점: 매우 빠릅니다.
    • 단점: 같은 열(column)의 비트가 짝수 개만큼 바뀌면 변경 사실을 감지하지 못합니다. (예: 0->1, 1->0으로 두 개가 바뀌면 XOR 결과는 같음). 신뢰성이 낮습니다.
  2. 덧셈(Addition) 체크섬:

    • 데이터 청크를 2의 보수 덧셈으로 다 더하고 오버플로우는 버립니다.
    • 장점: 역시 빠릅니다.
    • 단점: 데이터의 값은 그대로인데 순서가 바뀌거나(Shift), 특정 패턴으로 데이터가 바뀌면 감지하지 못할 수 있습니다.
  3. 플렛처(Fletcher) 체크섬:

    • XOR나 덧셈보다 조금 더 정교합니다. 두 개의 체크 바이트(s1s1, s2s2)를 운용합니다.
    • s1=(s1+di)mod255s1 = (s1 + d_i) \mod 255
    • s2=(s2+s1)mod255s2 = (s2 + s1) \mod 255
    • 위치에 따른 가중치가 부여되는 효과가 있어, 단순 덧셈보다 훨씬 강력하게 에러를 검출합니다.
  4. CRC (Cyclic Redundancy Check):

    • 데이터를 거대한 이진수로 보고, 미리 약속된 특정 값(Generator Polynomial)으로 나눈 나머지 를 체크섬으로 씁니다.
    • 네트워크와 스토리지 분야에서 가장 널리 쓰입니다.
    • 이진 나눗셈(Modulo-2 연산)은 하드웨어로 구현하기 매우 효율적이며, 연속적인 비트 오류(Burst Error)를 검출하는 데 탁월합니다.

일반적으로 스토리지 시스템은 충돌 확률을 최소화하면서도 계산 비용이 적당한 CRC 계열이나 Fletcher 알고리즘을 많이 사용합니다. 물론 보안이 중요한 곳에서는 MD5나 SHA 같은 암호학적 해시를 쓰기도 하지만, 파일 시스템의 속도를 위해선 너무 무겁습니다.


5. 체크섬을 디스크에 어떻게 배치할까? (Layout)

체크섬을 계산하는 건 알겠는데, 이걸 디스크 어디에 저장해야 할까요?

5.1. 섹터 내 포함 방식 (The 520-byte Sector)

가장 직관적인 방법은 각 섹터(512바이트) 바로 뒤에 체크섬(8바이트)을 붙이는 것입니다.

5.2. 별도의 체크섬 블록 방식

일반 소비자용 디스크(SATA 등)를 쓴다면 파일 시스템이 알아서 해야 합니다. 보통은 데이터 블록 nn개당 체크섬 블록 1개를 할당하여 따로 저장합니다.


6. 더 기괴한 오류들: 잘못된 위치 기록 & 기록 손실

단순히 데이터가 깨지는 것(Corruption) 외에도, 디스크 컨트롤러나 펌웨어의 버그로 인해 발생하는 황당한 오류들이 더 있습니다.

6.1. 잘못된 위치에 기록 (Misdirected Write)

데이터는 정상입니다. 체크섬도 그 데이터에 맞게 잘 계산되었습니다. 그런데 이 데이터가 엉뚱한 주소 에 저장되는 경우입니다.

6.2. 기록 작업의 손실 (Lost Write)

OS는 쓰기 명령을 내렸고, 디스크 컨트롤러는 "완료!"라고 응답했습니다. 하지만 실제로는 디스크에 기록되지 않은 경우입니다.


7. 스크러빙 (Scrubbing): 데이터 목욕 시키기

체크섬 시스템을 잘 갖췄다고 해도 문제가 하나 남습니다. "우리가 그 데이터를 읽지 않으면, 망가졌는지 어떻게 알지?" 대부분의 데이터는 자주 접근되지 않습니다(Cold Data). 그 사이에 비트가 썩어버리면(Bit rot), 나중에 중요할 때 읽으려다 낭패를 봅니다. 복구하려고 해도 미러링된 사본까지 같이 썩어있을 수도 있죠.

그래서 시스템은 주기적으로 스크러빙(Disk Scrubbing) 을 수행합니다.


8. 비용 (Overhead) 분석

이 모든 데이터 보호 기술에는 당연히 비용이 따릅니다. "세상에 공짜 점심은 없다(TNSTAAFL)"는 말은 시스템 프로그래밍에서도 진리입니다.

  1. 공간 오버헤드:

    • 디스크 공간: 4KB 블록당 8바이트 체크섬이라면 약 0.19%의 공간을 씁니다. 무시할 만한 수준입니다.
    • 메모리 공간: 읽은 데이터를 검증하기 위해 메모리에 체크섬을 올려야 하므로 약간의 메모리를 더 씁니다.
  2. 시간 오버헤드 (더 중요):

    • CPU: 모든 읽기/쓰기 작업마다 수학 연산(체크섬 계산)을 해야 합니다. 이를 줄이기 위해 OS는 데이터 복사(Buffer Copy)를 할 때 체크섬 계산을 동시에 수행하는 최적화를 하기도 합니다.
    • I/O: 체크섬이 데이터와 분리되어 저장된 경우, 체크섬 블록을 읽고 쓰기 위한 추가적인 I/O가 발생합니다. 스크러빙 또한 I/O 대역폭을 잡아먹습니다.

하지만 데이터가 소리 없이 망가져서 겪게 될 재앙(DB 손상, 파일 깨짐)에 비하면, 이 정도 오버헤드는 아주 저렴한 보험료 라고 할 수 있습니다.


9. 요약 및 결론

오늘 학습한 내용을 개발자의 언어로 요약해 보겠습니다.

  1. 디스크를 믿지 마십시오. write() 성공이 데이터의 영속성을 보장하지 않습니다. 디스크는 거짓말을 할 수 있습니다(Fail-Partial).
  2. LSE(물리적 배드 섹터)는 흔합니다. RAID 같은 중복(Redundancy) 시스템이 필수적입니다.
  3. 조용한 부패(Silent Corruption)가 가장 위험합니다. 디스크가 에러를 뱉지 않고 엉뚱한 값을 줍니다. 이를 잡으려면 체크섬(Checksum) 이 필수입니다.
  4. 단순 체크섬으로 부족한 경우가 있습니다.
    • Misdirected Write: 물리적 ID(디스크 번호, 오프셋)를 체크섬에 포함해야 합니다.
    • Lost Write: 체크섬을 데이터 자체가 아닌, 부모 노드(Inode 등)에 저장해야 합니다(ZFS 스타일).
  5. 데이터도 관리가 필요합니다. 주기적인 스크러빙(Scrubbing)으로 잠재된 에러를 미리미리 고쳐야 합니다.

참고